﻿/* ----------------------------------------------------------------------------
 * Copyright (c) Philip Stears 
 *
 * This source code is subject to terms and conditions of the Microsoft Public 
 * License (Ms-PL).
 * 
 * By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Public License.
 * 
 * To request a copy of the Microsoft Public License, send a request by email
 * to license@connectbasic.net.
 *
 * This notice must not be removed from this software.
 * ---------------------------------------------------------------------------- */

namespace ConnectBasic.Execution.Binding
{
    using System;
    using System.Reflection;
    using Microsoft.Linq.Expressions;
    using Microsoft.Scripting;

    internal class BasicInvokeMemberBinder : InvokeMemberBinder
    {
        public BasicInvokeMemberBinder(string name, CallInfo info)
            : base(name, true, info)
        {
        }

        public override DynamicMetaObject FallbackInvoke(DynamicMetaObject target, DynamicMetaObject[] args, DynamicMetaObject errorSuggestion)
        {
            throw new NotImplementedException();
        }

        public override DynamicMetaObject FallbackInvokeMember(DynamicMetaObject target, DynamicMetaObject[] args, DynamicMetaObject errorSuggestion)
        {
            DynamicMetaObject result;

            // COM object?
            if (ComBinder.TryBindInvokeMember(this, target, args, out result))
            {
                return result;
            }

            // Standard CLR object
            var members = target.Value.GetType().GetMember(this.Name, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public);

            foreach (var member in members)
            {
                if (!(member is MethodInfo))
                {
                    continue;
                }

                // Match parameters
                var mi = (MethodInfo)member;
                var pms = mi.GetParameters();
                var miArgs = new Expression[pms.Length];
                var restrictions = BindingRestrictions.GetInstanceRestriction(target.Expression, target.Value);

                var match = true;

                if (pms.Length < args.Length)
                {
                    continue;
                }

                // Standard arguments
                for (int i = 0; i < args.Length; i++)
                {
                    var arg = args[i];
                    var param = pms[i];

                    if (param.ParameterType.IsAssignableFrom(arg.RuntimeType))
                    {
                        miArgs[i] = arg.Expression;
                        restrictions =
                            restrictions.Merge(
                            BindingRestrictions.GetTypeRestriction(arg.Expression, param.ParameterType));
                    }
                    else
                    {
                        match = false;
                        break;
                    }
                }

                if (!match)
                {
                    continue;
                }

                // Optional arguments
                for (int i = args.Length; i < pms.Length; i++)
                {
                    miArgs[i] = Expression.Constant(pms[i].DefaultValue);
                }

                // Okay, looks good
                return new DynamicMetaObject(
                    Expression.Call(target.Expression, mi, miArgs),
                    restrictions);
            }

            // Return an exception
            return errorSuggestion ?? DynamicMetaObject.CreateThrow(target, args, typeof(NotImplementedException), (object[])null);
        }
    }
}
